#include <QtGui>
#include <QtNetwork>
#include "Client.h"
#include "Hand.h"
#include "game.h"
#include "Player.h"
#include "AIPlayer.h"

Client::Client(const QString &nif, game *_g, QString ip, QString _port, QString type)
{
    // Set local parameters for the client
    gui = _g;
    user = nif;
    ip_address = ip;
    port = _port.toInt();
    is_connected = false;
    gameInProgress = false;

    Hand hand;

    // Determine whether to create an AI or normal player based on user input.
    if( type == "AI")
        player = new AIPlayer(hand,0,gui,this);
    else
        player = new Player(hand,0,gui,this);

    socket = new QTcpSocket(this);

    crypto = new SimpleCrypt(0x6272657474667477);

    connect(socket, SIGNAL(connected()), this, SLOT(connected()));
    connect(socket, SIGNAL(readyRead()), this, SLOT(readMsg()));
    //connect(socket, SIGNAL(disconnected()), this, SLOT(restoreConnection()));

    qDebug() << QString("Client created for %1.").arg(user);
}

Client::~Client()
{
    socket->deleteLater();
}

void Client::connected()
{
    // This function is triggered when the socket emits a readyRead signal.
    qDebug() << QString("Connected to host.");
    is_connected = true;
}

void Client::readMsg()
{
    // Read the data from the TCPSocket into a byte array.
    QByteArray from_socket(socket->readAll());

    //qDebug() << from_socket;

    QByteArray decrpyted_msg(crypto->decryptToByteArray(from_socket));

    QString msg(decrpyted_msg);

    //qDebug() << msg;

    // If the message was damaged during transmission we need to detect that.
    bool msg_was_valid = true;

    msg = msg.replace("{", "|");
    msg = msg.replace("}", "|");

    // Parse the received message into a list of JSON Objects.
    QStringList split = msg.split("|");
    QStringList str;

    for (int i = 0; i < split.size(); ++i)
        if( split.at(i).length() > 4)
            str.append(QString(tr("{%1}").arg(split.at(i))));

    QList<QJsonObject> json_list;
    for( int i=0; i<str.size(); ++i)
    {
        QJsonDocument jdoc = QJsonDocument::fromJson(str.at(i).toUtf8());
        QJsonObject jobj = jdoc.object();
        json_list.append(jobj);
    }

    // Determine what to do with each message in the object list.
    for( int i=0; i<json_list.size(); ++i)
    {
        QJsonObject obj = json_list.at(i);
        QString type = obj["type"].toString().toUpper();

        if( type == "ACKNOWLEDGEMENT")
        {
            // Client typically doesn't respond to an acknowledgement.
        }
        else if( type == "REQUEST")
        {
            QString note = obj["note"].toString().toUpper();
            if( note == "BID")
            {
                player->getBid();
            }
            else if( note == "SCREEN_NAME")
            {

            }
            else if( note == "PLAY")
            {
                player->getMove(false);
            }
            else if( note == "DUMMY_PLAY")
            {
                player->getMove(true);
            }
        }
        else if( type == "PHASE")
        {
            QString note = obj["note"].toString().toUpper();
            if( note == "DEALING")
            {
                player->startDealing();
            }
            else if( note == "REDEALING")
            {
                player->startRedealing();
            }
            else if( note == "BIDDING")
            {
                player->startBidding();
            }
            else if( note == "PLAY")
            {
                player->startPlay();
            }
            else if( note == "RUBBER_DONE")
            {
                //
            }
            else if( note == "LOBBY")
            {

            }
        }
        else if( type == "BID_UPDATE")
        {
            QStringList bid_list;
            bid_list.append(obj["screen_name"].toString());
            bid_list.append(obj["bid_action"].toString());
            bid_list.append(obj["trick_count"].toString());
            bid_list.append(obj["trump_suit"].toString());
            player->sendBid(bid_list, 0);
        }
        else if( type == "DUMMY_IDENTIFICATION")
        {
            qDebug()<<"Dummy id start";
            QString dummy_name = obj["dummy"].toString();
            qDebug() << player_list;
            int owner = getUserByName( dummy_name);
            player->sendDummyId(owner);
            qDebug()<<"Dummy id end";

        }
        else if( type == "PLAY_UPDATE")
        {
            QString name = obj["screen_name"].toString();
            QString suit = obj["suit"].toString();
            QString value = obj["value"].toString();

            QStringList list;
            list.append(name);
            list.append(suit);
            list.append(value);

            int owner = getUserByName( name);

            player->sendMove(list, owner);
        }
        else if( type == "WON_UPDATE")
        {
            QString win_team = obj["winning_team"].toString();
            QString note = obj["Note"].toString().toUpper();
            QString win_player = obj["winning_player"].toString();

            if( note == "WON_GAME")
            {
                //player->sendGameWinner(win_team);
            }
            else if( note == "WON_TRICK")
            {
                //player->sendTrickWinner(win_team);
            }
            else if( note == "WON_RUBBER")
            {
                //
            }
            player->sendTrickWinner( win_team);
        }
        else if( type == "SCORE_UPDATE")
        {
            QString we_above = obj["we_above"].toString();
            QString we_below = obj["we_elow"].toString();
            QString they_above = obj["they_above"].toString();
            QString they_below = obj["they_below"].toString();

            player->sendScoreUpdate(we_above, we_below, they_above, they_below);
        }
        else if( type == "SERVER_ERROR")
        {
            QString name = obj["screen_name"].toString();
            QString note = obj["note"].toString();
        }
        else if( type == "PLAYER_LIST")
        {
            // Create a list of the players to send to the GUI.
            QStringList list;
            list.append(obj["north"].toString());
            list.append(obj["east"].toString());
            list.append(obj["south"].toString());
            list.append(obj["west"].toString());

            gui->showPlayerList( list);

            if( !gameInProgress)
            {
                // Check if the last player has joined the game.
                if( obj["west"].toString() != "")
                {
                    player_list.append(obj["north"].toString());
                    player_list.append(obj["east"].toString());
                    player_list.append(obj["south"].toString());
                    player_list.append(obj["west"].toString());

                    // Assign player id's based on where they are in the player list.
                    if( player_list.at(0) == user)
                        player->id = 0;
                    else
                    {

                        if( player_list.at(1) == user)
                            player->id = 1;
                        else
                        {

                            if( player_list.at(2) == user)
                                player->id = 2;
                            else
                                player->id = 3;
                        }
                    }
                    gameInProgress = true;
                }
            } else {
                emit exit(0);
            }
        }
        else if( type == "DEALT_CARDS")
        {
            QString cards = obj["card_array"].toString();
            QString note = obj["note"].toString().toUpper();

            Hand hand;
            QStringList split = cards.split(",");

            for (int i = 0; i < split.size(); ++i)
                if( split.at(i).length() > 2)
                {
                    QString c = split.at(i).right(2);
                    QString fi = split.at(i);
                    string si = fi.toUtf8().constData();
                    string s = c.toUtf8().constData();
                    hand.addCard(Card(si[0], player->id, player->id, s));
                }

            if( note == "OWN")
            {
                player->setLocalHand(hand);
                player->sendHand();
            }
            else
            {
                player->sendDummyCards(hand);
            }
        }
        else if( type == "CONTRACT")
        {
            QStringList contract_list;
            contract_list.append(obj["trick_count"].toString());
            contract_list.append(obj["trump_suit"].toString());
            player->sendContract(contract_list);
        }
        else if( type == "STATUS")
        {
            QStringList status;
            status.append(obj["trick_number"].toString());
            status.append(obj["deal_number"].toString());
            status.append(obj["game_number"].toString());
            player->sendGameStatus( status);
        }
        else
        {
            // If all the checks fail, the message could not be parsed.
            qDebug() << QString("Message received: UNKNOWN");
            qDebug() << msg << endl;
            msg_was_valid = false;
        }

        if( msg_was_valid)
        {
            // For debugging purposes.
            qDebug() << QString("Message received: %1").arg(type);
            qDebug() << msg << endl;
            // We must acknowlege for each message we are able to parse.
            sendAcknowledge(1,0);
        }
    }
}

void Client::closeSocket()
{
    socket->close();
    qDebug() << QString("Connection closed by client.");
    connectionClosedByServer();
    is_connected = false;
}

void Client::connectionClosedByServer()
{
    qDebug() << QString("Connection closed by server...");
}

void Client::restoreConnection()
{
    // Connects the client's socket to the host server.
    socket->connectToHost(ip_address, port);
    socket->waitForConnected(2000);
}

int Client::getUserByName( QString name)
{
    // Search the player list for the matching name and return it's
    // direction. North=0, South=2, East=1, West=3.
    int owner = -1;
    for( int i=0; i<4; i++)
    {
        if( player_list.at(i) == name)
        {
            owner = i;
            break;
        }
    }

    return owner;
}

bool Client::requestJoin()
{
    // Ask the server we are connected to if we can join the game.
    QJsonObject json;
    json.insert("type", QJsonValue("join"));
    json.insert("screen_name", QJsonValue(user));

    if( writeJSON(json))
        return true;

    return false;
}

bool Client::requestNewName(QString new_name)
{
    // Ask the server if we can change the screen name.
    QJsonObject json;
    json.insert("type", QJsonValue("new_screen_name"));
    json.insert("screen_name", QJsonValue(user));
    json.insert("new_screen_name", QJsonValue(new_name));

    if( writeJSON(json))
        return true;

    return false;
}

bool Client::requestSlotChange(QString dir)
{
    // Ask the server if we can change direction.
    QJsonObject json;
    json.insert("type", QJsonValue("request_slot_change"));
    json.insert("screen_name", QJsonValue(user));
    json.insert("slot",  QJsonValue(dir));

    if( writeJSON(json))
        return true;

    return false;
}

bool Client::sendBid(QStringList list)
{   
    // Send the client's bid to the server.
    QJsonObject json;
    json.insert("type", QJsonValue("bid"));
    json.insert("bid_action", QJsonValue(list.at(0)));
    json.insert("trick_count", QJsonValue(list.at(1)));
    json.insert("screen_name", QJsonValue(user));
    json.insert("trump_suit",  QJsonValue(list.at(2)));

    if( writeJSON(json))
        return true;

    return false;
}

bool Client::sendAcknowledge(int success, int error_code)
{
    // Send a general acknowlegede message.
    QJsonObject json;
    json.insert("type", QJsonValue("acknowledgement"));
    json.insert("note", QJsonValue(success));
    json.insert("error_code", QJsonValue(error_code));

    if( writeJSON(json))
        return true;

    return false;
}

bool Client::sendPlayCard(QString suit, QString value)
{
    // Send the move that the player made to the server.
    QJsonObject json;
    json.insert("type", QJsonValue("play_card"));
    json.insert("suit", QJsonValue(suit));
    json.insert("screen_name", QJsonValue(user));
    json.insert("value", QJsonValue(value));

    if( writeJSON(json))
        return true;

    return false;
}

bool Client::sendQuit()
{
    // Ask the server if the client can close the connection.
    QJsonObject json;
    json.insert("type", QJsonValue("client_quits"));
    json.insert("screen_name", QJsonValue(user));

    writeJSON(json);
    closeSocket();
}

bool Client::sendError(QString error_msg)
{
    // Send an error message back to the server.
    QJsonObject json;
    json.insert("type", QJsonValue("client_error"));
    json.insert("note", QJsonValue(error_msg));
    json.insert("screen_name", QJsonValue(user));

    if( writeJSON(json))
        return true;

    return false;
}

bool Client::sendMove(Card card)
{
    // Similar to sendPlayCard, but takes a card object instead of QStrings.
    QJsonObject json;
    json.insert("type", QJsonValue("play_card"));
    json.insert("suit", QJsonValue(QString(card.suit)));
    json.insert("value", QJsonValue(QString::fromStdString(card.num)));
    json.insert("screen_name", QJsonValue(player_list.at(card.owner))); // card.owner was previously user

    if( writeJSON(json))
        return true;

    return false;
}

bool Client::writeJSON(QJsonObject json)
{
    // Writes the JSON object to the network.
    if( !is_connected) {
        qDebug() << QString("Client must first establish connection to a "
                            "host before this operation can be completed.");
        return false;
    }

    QJsonDocument doc(json);

    QByteArray bytes = doc.toJson();

    bytes = crypto->encryptToByteArray(bytes);

    //QString req = QString::fromUtf8(doc.toJson());
    //std::string utf = req.toUtf8().constData();

    //socket->write(utf.data());

    socket->write(bytes);
    if (socket->waitForBytesWritten(2000))
        return true;
        //qDebug() << ("C|Client sent data.");
    else
        return false;

    return true;
}
